﻿using Microsoft.AspNetCore.Http;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Infrastructure;
using PMS.Core.Utils;
using PMS.Core.Utils.Http;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Medias;
using PMS.Data.Entities.Settings;
using PMS.Services.EntityServices.Logging;
using PMS.Services.Utils;
using QingStor_SDK_NET.Common;
using QingStor_SDK_NET.Service;
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace PMS.Services.EntityServices.Medias
{
    /// <summary>
    /// MediaFileService with medias stored in QingStor using qingstor_netcore_sdk
    /// </summary>
    public class MediaFileQingStorService : BaseMediaFileService
    {
        #region Fileds
        private readonly QingStorSettings _qingStorSettings;
        private readonly CConfig _qsConfig;
        private readonly ILogger _logger;
        private readonly IPMSFileProvider _fileProvider;
        private readonly IEventDispatcher _eventDispatcher;
        private readonly HttpClient _httpClient;
        private readonly IRepository<MediaFile> _mediaFileRepository;
        #endregion

        #region Ctor

        public MediaFileQingStorService(MediaSettings mediaSettings,
            QingStorSettings qingStorSettings,
            ILogger logger,
            IEventDispatcher eventDispatcher,
            IHttpClientFactory httpClientFactory,
            IHttpContextAccessor httpContextAccessor,
            IRepository<MediaFile> mediaFileRepository)
            : base(mediaSettings,
                  eventDispatcher,
                  httpContextAccessor,
                  mediaFileRepository)
        {
            _qingStorSettings = qingStorSettings;
            _logger = logger;
            _fileProvider = CommonHelper.DefaultFileProvider;
            _eventDispatcher = eventDispatcher;
            _mediaFileRepository = mediaFileRepository;
            _httpClient = httpClientFactory.CreateClient(HttpDefaultConfigs.DefaultHttpClient);
            _qsConfig = LoadQingStorConfig();
        }
        #endregion

        #region Utils
        protected virtual CConfig LoadQingStorConfig()
        {
            var filePath = _fileProvider.MapPath("~/App_Data/QingStorSettings.json");
            //try to get config info from the JSON file
            var cfgText = _fileProvider.FileExists(filePath)
                        ? _fileProvider.ReadAllText(filePath, Encoding.UTF8)
                        : string.Empty;
            return string.IsNullOrEmpty(cfgText) ? null : new CConfig(cfgText, ConfigLoadMode.JsonStr);
        }
         
        protected virtual (MediaFileType category, string vPath) GetMediaFileVirtalPath(MediaFile file)
        {
            var category = MimeTypesManager.Instance.GetFileCategoryFromExtName(file.ExtensionName);
            string catName = Enum.ToObject(typeof(MediaFileType), category).ToString();
            string reqFileVirtualPath = $"/{catName}/{file.SeoFilename}.{file.ExtensionName}";
            return (category, reqFileVirtualPath);
        }

        /// <summary>
        /// 此方法只适用于通过URL（或QingStor上设置的自定义域名）来访问 Bucket 存储空间上的文件
        /// 通过如 http://bucketname.zoneid.qingstor.com/filename 来直接访问文件资源
        /// </summary>
        /// <returns></returns>
        protected virtual string CreateURI()
        {
            //是否可以通过绑定的自定义域名访问
            if (!string.IsNullOrEmpty(_qingStorSettings.SelfBindRequestUrl) && CommonHelper.IsValidURI(_qingStorSettings.SelfBindRequestUrl))
                return _qingStorSettings.SelfBindRequestUrl;

            string strURL = "";

            // Filt "www." of Host
            string strHost = string.IsNullOrEmpty(_qsConfig.Host)
                            ? ""
                            : _qsConfig.Host;
            strHost = strHost.ToLower();
            if (strHost.StartsWith("www."))
            {
                strHost = strHost.Substring(4);
            }
            strURL = string.Format("{0}", strHost);
            // Zone Exists
            if (!string.IsNullOrEmpty(_qingStorSettings.ZoneName))
                strURL = string.Format("{0}.", _qingStorSettings.ZoneName) + strURL;
            // Bucket Exists
            if (!string.IsNullOrEmpty(_qingStorSettings.BucketName))
                strURL = string.Format("{0}.", _qingStorSettings.BucketName) + strURL;

            // Protocol
            string strProtocol = !string.IsNullOrEmpty(_qsConfig.Protocol)
                               ? _qsConfig.Protocol
                               : "http";
            strURL = string.Format("{0}://", strProtocol) + strURL;

            return strURL;
        }
        /// <summary>
        /// Loads a picture from file Only For ImageFile
        /// </summary>
        /// <param name="file">MediaFile entity</param>
        /// <returns>Picture binary</returns>
        protected virtual byte[] LoadPictureRemote(MediaFile file)
        {
            var fileInfo = GetMediaFileVirtalPath(file);
            if (fileInfo.category != MediaFileType.Image) throw new PMSException("Only For ImageFile Use", SystemLogLevel.Error);

            //if (fileInfo.vPath.StartsWith('/'))
            //    fileInfo.vPath = fileInfo.vPath.Substring(1);
            CGetObjectOutput getResponse = null;
            using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
            {
                CGetObjectInput inputParam = new CGetObjectInput()
                {
                    response_content_encoding = Encoding.UTF8.ToString(),
                    response_content_type = MimeTypesManager.Instance.GetFileMimeTypeFromExtName(file.ExtensionName),
                };
                getResponse = bucket.GetObject(fileInfo.vPath, inputParam);
                if (getResponse.StatusCode == ConstDef.RESPONSE_STATUS_OK && getResponse.Body != null)
                {
                    var bytes = default(byte[]);
                    using (var memstream = new MemoryStream())
                    {
                        var buffer = new byte[getResponse.Content_Length];
                        var bytesRead = default(int);
                        while ((bytesRead = getResponse.Body.BaseStream.Read(buffer, 0, buffer.Length)) > 0)
                            memstream.Write(buffer, 0, bytesRead);
                        bytes = memstream.ToArray();
                    }
                    return bytes;
                }
            }
               
            _logger.Error("QingStor Server response StatusCode: " + getResponse.StatusCode.ToString());
            throw new PMSException(getResponse.ErrorCode, SystemLogLevel.Error);
        }

        protected void DeleteRemoteMediaFile(CBucket bucket, MediaFile targetFile)
        {
            if (!string.IsNullOrEmpty(targetFile.VirtualPath))
            {
                var originFileInfo = GetMediaFileVirtalPath(targetFile);
                CDeleteObjectOutput DeleteObjectOutput = bucket.DeleteObject(originFileInfo.vPath);
                if (DeleteObjectOutput.StatusCode != ConstDef.RESPONSE_STATUS_FILE_DELETED)
                {
                    _logger.Error($"QingStor DeleteObject Sdk Error before update. MediaFileId: {targetFile.Id}, StatusCode: {DeleteObjectOutput.StatusCode}");
                }
                try
                {
                    //delete thumbs if file is Picture
                    DeletePictureThumbs(targetFile);
                }
                catch (DirectoryNotFoundException)
                {
                    //DoNothing
                }
                catch (UnauthorizedAccessException ex)
                {
                    _logger.Error("ThumbImageFile Delete From MediaFileQingStorService Error, Delete them by yourself", ex);
                }
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Gets the loaded picture binary only when MediaFile is Image
        /// depending on media storage settings
        /// </summary>
        /// <param name="imageFile">MediaFile</param>
        /// <returns>Picture binary</returns>
        public override byte[] LoadPictureBinary(MediaFile imageFile)
        {
            if (imageFile == null)
                throw new ArgumentNullException(nameof(imageFile));

            if (imageFile.StoreMode != MediaFileStoreMode.QingStor)
                throw new PMSException("LocalStorage Image Only", SystemLogLevel.Error);
            return LoadPictureRemote(imageFile);
        }

        /// <summary>
        /// Deletes a MediaEntity
        /// </summary>
        /// <param name="file">MediaFile</param>
        public override bool DeleteMediaFile(MediaFile file)
        {
            if (file == null)
                throw new ArgumentNullException(nameof(file));

            //delete file remote
            using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
            {
                var fileInfo = GetMediaFileVirtalPath(file);
                CDeleteObjectOutput DeleteObjectOutput = bucket.DeleteObject(fileInfo.vPath);
                if (DeleteObjectOutput.StatusCode == ConstDef.RESPONSE_STATUS_FILE_DELETED)
                {
                    try
                    {
                        //delete thumbs if file is Picture
                        DeletePictureThumbs(file);
                    } 
                    catch(DirectoryNotFoundException)
                    {
                        //DoNothing
                    }
                    catch (UnauthorizedAccessException ex)
                    {
                        _logger.Error("ThumbImageFile Delete From MediaFileQingStorService Error, Delete them by yourself", ex);
                    }

                    //delete from database
                    _mediaFileRepository.Delete(file);
                    //event notification
                    _eventDispatcher.EntityDeleted(file);

                    return true;
                }
                string errMsg = $"Delete File Remote via qing_stor_sdk error: StatusCode: {DeleteObjectOutput.StatusCode}"
                              + (string.IsNullOrEmpty(DeleteObjectOutput.ErrorCode) ? "" : $", ErrorCode: {DeleteObjectOutput.ErrorCode}");
                _logger.Error(errMsg);
                return false;
            }
        }

        /// <summary>
        /// same as (bool IsDefault, string FileUrl) GetMediaFileUrl(int fileId, int targetSize = 0); 
        /// </summary>
        public override (bool IsDefault, string FileUrl, string thumbFileUrl) GetMediaFileUrl(ref MediaFile mediaFile, int targetSize = 0)
        {
            if (mediaFile == null)
                //throw new ArgumentNullException("mediaFile");
                return (true, GetDefaultPictureUrl(), GetDefaultPictureUrl(targetSize));

            var fileCategory = MimeTypesManager.Instance.GetFileCategoryFromExtName(mediaFile.ExtensionName);
            if (fileCategory == MediaFileType.Unknown)
                throw new PMSException($"UnSupportMediaFile Stored with MediaFile id: {mediaFile.Id}", SystemLogLevel.Error);

            if (fileCategory == MediaFileType.Image && targetSize > 0)
            {
                try
                {
                    var thumbFileName = $"{mediaFile.Id:0000000}_{targetSize}.{mediaFile.ExtensionName}";
                    var thumbFilePath = GetThumbLocalPath(thumbFileName);
                    if (!_fileProvider.FileExists(thumbFilePath))
                    {
                        var imgbytes = LoadPictureBinary(mediaFile);
                        var pictureBinary = ResizePicture(imgbytes, true, targetSize);
                        SaveThumb(thumbFilePath, pictureBinary);
                    }
                    var url = GetMediaFilePathUrl(_fileProvider.GetVirtualPath(thumbFilePath).TrimStart('~')) + thumbFileName;
                    return (false, mediaFile.VirtualPath, url);
                }
                catch(PMSException)
                {
                    return (true, GetDefaultPictureUrl(), GetDefaultPictureUrl(targetSize));
                }
            }

            if (string.IsNullOrEmpty(mediaFile.VirtualPath))
                return (true, GetDefaultPictureUrl(), "");

            return (false, mediaFile.VirtualPath, "");
        }
        /// <summary>
        /// Shortcut for insert a media file as Image
        /// Inserts a picture from pictureBinary, For scenario like base64 image
        /// </summary>
        /// <param name="pictureBinary">The picture binary</param>
        /// <param name="mimeType">The picture MIME type</param>
        /// <param name="useage">The useage of the image</param>
        /// <param name="title">"title" attribute for Image</param>
        /// <param name="description">"description" attribute for Image</param>
        /// <param name="imagelink">imagelink for image "href"</param>
        /// <returns>MediaFile</returns>
        public override MediaFile InsertPicture(byte[] pictureBinary, string mimeType, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            mimeType = CommonHelper.EnsureNotNull(mimeType);
            mimeType = CommonHelper.EnsureMaximumLength(mimeType, 20);

            if (MimeTypesManager.Instance.GetFileCategoryByMimeType(mimeType) != MediaFileType.Image)
                return null;
            //Resize Image when Input Image larger than _mediaSetting.MaximumImageWidth
            var resizedImgBinary = ResizePicture(pictureBinary);
            resizedImgBinary = CompressPicture(resizedImgBinary);
            var picture = new MediaFile
            {
                SeoFilename = GenerateUniqueMediaFileName(),
                ExtensionName = GetOutputFileExtensionFromMimeType(mimeType),
                Useage = useage,
                Title = title,
                Description = description,
                ImageLink = imagelink,
                StoreMode = MediaFileStoreMode.QingStor,
                CreatedOnTimeUtc = DateTime.UtcNow,
                UpdatedOnTimeUtc = DateTime.UtcNow
            };
            if (string.IsNullOrEmpty(picture.ExtensionName))
                throw new PMSException("Unsupport Image Extension insert", SystemLogLevel.Error);
            else picture.ExtensionName = picture.ExtensionName.ToLowerInvariant();

            try
            {
                using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
                {
                    CPutObjectInput inputPara = new CPutObjectInput
                    {
                        Content_Length = resizedImgBinary.Length,
                        Content_Type = mimeType,
                        Body = new MemoryStream(resizedImgBinary) { Position = 0 }
                    };
                    var imgInfo = GetMediaFileVirtalPath(picture);
                    CPutObjectOutput PutObjectOutput = bucket.PutObject(imgInfo.vPath, inputPara);
                    if (PutObjectOutput.StatusCode == ConstDef.RESPONSE_STATUS_FILE_CREATED)
                    {
                        picture.VirtualPath = CreateURI() + imgInfo.vPath;
                        _mediaFileRepository.Insert(picture);
                        //event notification
                        _eventDispatcher.EntityInserted(picture);
                        return picture;
                    }
                    string errMsg = $"QingStor PutObject: StatusCode: {PutObjectOutput.StatusCode}";
                    if (!string.IsNullOrEmpty(PutObjectOutput.ErrorCode)) errMsg += ", ErrorCode: " + PutObjectOutput.ErrorCode;
                    throw new PMSException(errMsg, SystemLogLevel.Error);
                }
            } 
            catch(Exception ex)
            {
                _logger.Error("Insert Picture Error.", ex);
                return null;
            }
        }

        /// <summary>
        /// Shortcut for Updates a media file as Image
        /// Update a picture from pictureBinary, For scenario like base64 image
        /// It can also update Entity only if pictureBinary is null
        /// </summary>
        /// <param name="pictureId">The picture identifier to be upload</param>
        /// <param name="mimeType">The input picture MIME type</param>
        /// <param name="pictureBinary">The input picture binary</param>
        /// <param name="useage">The useage of the image</param>
        /// <param name="title">"title" attribute for Image</param>
        /// <param name="description">"description" attribute for Image</param>
        /// <param name="imagelink">imagelink for image "href"</param>
        /// <returns>Picture</returns>
        public override MediaFile UpdatePicture(int pictureId, string mimeType, byte[] pictureBinary = null, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            if(MimeTypesManager.Instance.GetFileCategoryByMimeType(mimeType) != MediaFileType.Image)
                throw new PMSException("UpdatePicture Method is a Shortcut for Updates a media file as Image, try UpdateMediaFile Instead.", SystemLogLevel.Warning);

            MediaFile targetPicture = GetMediaFileById(pictureId);
            if (targetPicture == null) 
                return null;
            if (pictureBinary != null)
            {
                using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
                {
                    //delete file remote within a isolate thread
                    Task.Run(() =>
                    {
                        DeleteRemoteMediaFile(bucket, targetPicture);
                    });

                    //无论服务器文件删除与否，更新链接
                    //Resize Image when Input Image larger than _mediaSetting.MaximumImageWidth
                    var resizedImgBinary = ResizePicture(pictureBinary);
                    resizedImgBinary = CompressPicture(resizedImgBinary);
                    CPutObjectInput inputPara = new CPutObjectInput
                    {
                        Content_Length = resizedImgBinary.Length,
                        Content_Type = mimeType,
                        Body = new MemoryStream(resizedImgBinary) { Position = 0 }
                    };
                    string outputExt = GetOutputFileExtensionFromMimeType(mimeType);
                    targetPicture.ExtensionName = outputExt;
                    targetPicture.SeoFilename = GenerateUniqueMediaFileName();
                    var imgInfo = GetMediaFileVirtalPath(targetPicture);
                    CPutObjectOutput PutObjectOutput = bucket.PutObject(imgInfo.vPath, inputPara);
                    targetPicture.VirtualPath = PutObjectOutput.StatusCode == ConstDef.RESPONSE_STATUS_FILE_CREATED
                                            ? CreateURI() + imgInfo.vPath
                                            : string.Empty;
                }
            }
            //targetPicture.Id = pictureId;
            targetPicture.Useage = useage;
            targetPicture.Title = title;
            targetPicture.Description = description;
            targetPicture.ImageLink = imagelink;
            targetPicture.StoreMode = MediaFileStoreMode.QingStor;
            return UpdateMediaFileInfoOnly(targetPicture);
        }

        /// <summary>
        /// Inserts a MediaFile
        /// </summary>
        /// <param name="formFile">Form file</param>
        /// <param name="useage">The useage of the MediaFile</param>
        /// <param name="title">"title" attribute for MediaFile</param>
        /// <param name="description">"description" attribute for MediaFile</param>
        /// <param name="imagelink">imagelink for image "href" if its a Image</param>
        /// <returns>MediaFile</returns>
        public override MediaFile InsertMediaFile(IFormFile formFile, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            var fileName = formFile.FileName;
            string contentType = string.Empty;
            //remove path (passed in IE)
            fileName = _fileProvider.GetFileName(fileName);
            string fileExtension;
            if (!string.IsNullOrEmpty(fileName))
                fileExtension = GetOutputFileExtensionFromFileName(fileName);
            else
            {
                try
                {
                    contentType = formFile.ContentType;
                    fileExtension = GetOutputFileExtensionFromMimeType(contentType);
                }
                catch (ArgumentNullException)
                {
                    throw new PMSException("Cannot auto detect file extension while both fileName and contenType are Empty.", SystemLogLevel.Error);
                }
            }

            if (!string.IsNullOrEmpty(fileExtension))
                fileExtension = fileExtension.ToLowerInvariant();
            else throw new PMSException("MediaFile ExtensionName cannot be Empty", SystemLogLevel.Error);

            byte[] fileBytes = GetMediaFileBits(formFile);
            //Use ShortCut Insert for ImageFile Insert for further resize/compress 
            if (MimeTypesManager.Instance.GetFileCategoryFromExtName(fileExtension) == MediaFileType.Image)
            {
                contentType = string.IsNullOrEmpty(formFile.ContentType)
                            ? MimeTypesManager.Instance.GetFileMimeTypeFromExtName(fileExtension)
                            : formFile.ContentType;
                return InsertPicture(fileBytes, contentType, useage, title, description, imagelink);
            }

            var file = new MediaFile
            {
                SeoFilename = GenerateUniqueMediaFileName(),
                ExtensionName = fileExtension,
                Useage = useage,
                Title = title,
                Description = description,
                ImageLink = imagelink,
                StoreMode = MediaFileStoreMode.QingStor,
                CreatedOnTimeUtc = DateTime.UtcNow,
                UpdatedOnTimeUtc = DateTime.UtcNow
            };
            try
            {
                using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
                {
                    CPutObjectInput inputPara = new CPutObjectInput
                    {
                        Content_Length = fileBytes.Length,
                        Content_Type = contentType,
                        Body = new MemoryStream(fileBytes) { Position = 0 }
                    };
                    var fileInfo = GetMediaFileVirtalPath(file);
                    CPutObjectOutput PutObjectOutput = bucket.PutObject(fileInfo.vPath, inputPara);
                    if (PutObjectOutput.StatusCode == ConstDef.RESPONSE_STATUS_FILE_CREATED)
                    {
                        file.VirtualPath = CreateURI() + fileInfo.vPath;
                        _mediaFileRepository.Insert(file);
                        //event notification
                        _eventDispatcher.EntityInserted(file);
                        return file;
                    }
                    throw new PMSException(PutObjectOutput.ErrorCode, SystemLogLevel.Error);
                }
            }
            catch (Exception ex)
            {
                _logger.Error("Insert Picture Error.", ex);
                return null;
            }
        }

        /// <summary>
        /// Update a MediaFile
        /// It can also update Entity if formfile is null
        /// </summary>
        /// <param name="fileId">The mediafile identifier</param>
        /// <param name="formFile">Form file</param>
        /// <param name="useage">The useage of the MediaFile</param>
        /// <param name="title">"title" attribute for MediaFile</param>
        /// <param name="description">"description" attribute for MediaFile</param>
        /// <param name="imagelink">imagelink for image "href" if its a Image</param>
        /// <returns>MediaFile</returns>
        public override MediaFile UpdateMediaFile(int fileId, IFormFile formFile, string useage = null,
            string title = null, string description = null, string imagelink = null)
        {
            var targetFile = GetMediaFileById(fileId);
            if (targetFile == null) return null;
            var contentType = string.Empty;
            var fileName = formFile.FileName;
            //remove path (passed in IE)
            fileName = _fileProvider.GetFileName(fileName);
            string fileExtension;
            if (!string.IsNullOrEmpty(fileName))
                fileExtension = GetOutputFileExtensionFromFileName(fileName);
            else
            {
                try
                {
                    contentType = formFile.ContentType;
                    fileExtension = GetOutputFileExtensionFromMimeType(contentType);
                }
                catch (ArgumentNullException)
                {
                    throw new PMSException("Cannot auto detect file extension while both fileName and contenType are Empty.", SystemLogLevel.Error);
                }
            }

            if (!string.IsNullOrEmpty(fileExtension))
                fileExtension = fileExtension.ToLowerInvariant();
            else throw new PMSException("MediaFile ExtensionName cannot be Empty", SystemLogLevel.Error);

            byte[] fileBytes = GetMediaFileBits(formFile);
            //Use ShortCut Image Update for ImageFile Update for further resize/compress 
            if (MimeTypesManager.Instance.GetFileCategoryFromExtName(fileExtension) == MediaFileType.Image)
            {
                contentType = string.IsNullOrEmpty(formFile.ContentType)
                            ? MimeTypesManager.Instance.GetFileMimeTypeFromExtName(fileExtension)
                            : formFile.ContentType;
                return UpdatePicture(fileId, contentType, fileBytes, useage, title, description, imagelink);
            }

            if(fileBytes == null || fileBytes.Length <= 0)
            {
                using (CBucket bucket = new CBucket(_qsConfig, _qingStorSettings.BucketName, _qingStorSettings.ZoneName, _httpClient))
                {
                    //delete file remote within a isolate thread
                    Task.Run(() =>
                    {
                        DeleteRemoteMediaFile(bucket, targetFile);
                    });
                    //无论服务器文件删除与否，更新链接
                    CPutObjectInput inputPara = new CPutObjectInput
                    {
                        Content_Length = fileBytes.Length,
                        Content_Type = contentType,
                        Body = new MemoryStream(fileBytes) { Position = 0 }
                    };
                    targetFile.ExtensionName = fileExtension;
                    targetFile.SeoFilename = GenerateUniqueMediaFileName();
                    var fileInfo = GetMediaFileVirtalPath(targetFile);
                    CPutObjectOutput PutObjectOutput = bucket.PutObject(fileInfo.vPath, inputPara);
                    targetFile.VirtualPath = PutObjectOutput.StatusCode == ConstDef.RESPONSE_STATUS_FILE_CREATED
                                            ? CreateURI() + fileInfo.vPath
                                            : string.Empty;
                }
            }

            targetFile.Useage = useage;
            targetFile.Title = title;
            targetFile.Description = description;
            targetFile.ImageLink = imagelink;
            targetFile.StoreMode = MediaFileStoreMode.QingStor;
            return UpdateMediaFileInfoOnly(targetFile);
        }

        #endregion
    }
}
